package cn.iotnc.camera.ui.widget

import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.DrawableRes
import androidx.fragment.app.Fragment
import cn.iotnc.camera.R
import cn.iotnc.camera.databinding.LoadingLayoutEmptyBinding
import cn.iotnc.camera.databinding.LoadingLayoutErrorBinding
import cn.iotnc.camera.databinding.LoadingLayoutLoadingBinding

sealed class State
object None : State()
object Loading : State()
object Content : State()
object Empty : State()
object Error : State()


class StateLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : FrameLayout(context, attrs, defStyle) {
    private val layoutInflater by lazy {
        LayoutInflater.from(context)
    }
    var state: State = None // default state
    private val loadingBinding by lazy {
        LoadingLayoutLoadingBinding.inflate(layoutInflater, this, false)
    }
    private val emptyBinding by lazy {
        LoadingLayoutEmptyBinding.inflate(layoutInflater, this, false)
    }
    private val errorBinding by lazy {
        LoadingLayoutErrorBinding.inflate(layoutInflater, this, false)
    }
    lateinit var contentView: View

    fun wrap(view: View): StateLayout {
        emptyBinding.apply {
            root.visibility = View.INVISIBLE
            root.alpha = 0f
        }
        errorBinding.apply {
            root.visibility = View.INVISIBLE
            root.alpha = 0f
            retryButton.setOnClickListener { retry() }
        }

        loadingBinding.apply {
            root.visibility = View.INVISIBLE
            root.alpha = 0f
        }

        prepareStateView()

        view.visibility = View.INVISIBLE
        view.alpha = 0f
        if (view.parent == null) {
            //no attach parent.
            addView(view, 0, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
            contentView = view
        } else {
            // 1.remove self from parent
            val parent = view.parent as ViewGroup
            val lp = view.layoutParams
            val index = parent.indexOfChild(view)
            parent.removeView(view)
            // 2.wrap view as a parent
            addView(view, 0, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))

            // 3.add this to original parent，暂不支持parent为ConstraintLayout
            parent.addView(this, index, lp)
            contentView = view
        }
        switchLayout(Content)
        return this
    }

    fun wrap(activity: Activity): StateLayout =
        wrap((activity.findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0))

    fun wrap(fragment: Fragment): StateLayout = wrap(fragment.requireView())

    private fun prepareStateView() {
        addView(emptyBinding.root)
        addView(errorBinding.root)
        addView(loadingBinding.root)
    }

    private fun switchLayout(s: State) {
        if (state == s) return
        state = s
        when (state) {
            is Loading -> {
                switch(loadingBinding.root)
            }
            is Empty -> {
                switch(emptyBinding.root)
            }
            is Error -> {
                switch(errorBinding.root)
            }
            is Content -> {
                switch(contentView)
            }
        }
    }

    fun showLoading(
        loadingText: String = resources.getString(R.string.loading),
        isShowText: Boolean = true
    ): StateLayout {
        switchLayout(Loading)
        loadingBinding.loadingText.apply {
            visibility = if (isShowText) View.VISIBLE else View.GONE
            text = loadingText
        }
        return this
    }

    fun showContent(): StateLayout {
        switchLayout(Content)
        return this
    }

    fun showEmpty(
        emptyTitle: String = resources.getString(R.string.loading_empty),
        @DrawableRes emptyIcon: Int = R.drawable.ic_empty
    ): StateLayout {
        switchLayout(Empty)
        emptyBinding.emptyIcon.setImageResource(emptyIcon)
        emptyBinding.emptyText.text = emptyTitle
        return this
    }

    fun showError(
        errorTitle: String = context.getString(R.string.loading_error_title),
        retryText: String = context.getString(R.string.loading_retry),
        @DrawableRes errorIcon: Int = R.drawable.ic_error
    ): StateLayout {
        switchLayout(Error)
        errorBinding.errorIcon.setImageResource(errorIcon)
        errorBinding.errorText.text = errorTitle
        errorBinding.retryButton.text = retryText
        return this
    }

    private fun switch(v: View?) {
        if (switchTask != null) {
            removeCallbacks(switchTask)
        }
        switchTask = SwitchTask(v!!)
        post(switchTask)
    }

    private fun retry() {
        showLoading()
        mRetryAction?.invoke()
    }

    var switchTask: SwitchTask? = null

    inner class SwitchTask(val target: View) : Runnable {
        override fun run() {
            for (i in 0..childCount) {
                if (state == Loading && getChildAt(i) == contentView) continue
                hideAnim(getChildAt(i))
            }
            showAnim(target)
        }
    }

    private fun showAnim(v: View?) {
        if (v == null) return
        v.animate().cancel()
        v.animate().alpha(1f)
            .withStartAction {
                v.visibility = View.VISIBLE
            }
            .start()
    }

    private fun hideAnim(v: View?) {
        if (v == null) return
        v.animate().cancel()
        v.animate().alpha(0f)
            .withEndAction {
                v.visibility = View.INVISIBLE
            }
            .start()
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        if (state == Loading && loadingBinding.root.visibility == View.VISIBLE) return true
        return super.dispatchTouchEvent(ev)
    }

    private var mRetryAction: (() -> Unit)? = null

    fun retry(
        retryAction: (() -> Unit)? = null
    ): StateLayout {
        mRetryAction = retryAction
        return this
    }
}